from service import Service
import util
import stackless

class Action(Service):
    _subscriptions = {}
    
    def __init__(self, object, name):
        self.object = object
        self.name = name
        self._actionCount += 1
        self.channel = stackless.channel()
        
        self.receiveFunctions = util.getFunctions(self, "receive_")
        for cmd, func in self.receiveFunctions.iteritems():
            self.subscribe(cmd,self.channel)
        
        Service.__init__(self)
        
    def __call__(self, **args):
        self.listenTasklet = stackless.tasklet(self.listen)
        self.listenTasklet()
        self.tasklets.append(self.listenTasklet)
        return Service.__call__(self, **args)
    
    def listen(self):
        while True:
            cmd, args = self.channel.receive()
            if cmd in self.receiveFunctions:
                #self.log("receive",cmd,args)
                self.receiveFunctions[cmd](args)
                
    @classmethod
    def subscribe(self, cmd, f):
        from networkServer import networkServer
        self._subscriptions.setdefault(cmd,set()).add(f)
        networkServer.send("subscribe",cmd)
        
    @classmethod
    def unsubscribe(self, cmd, f):
        from networkServer import networkServer
        assert cmd in self._subscriptions and f in self._subscriptions[cmd]
        self._subscriptions[cmd].remove(f)
        networkServer.send("unsubscribe",cmd)
        
    @classmethod
    def publish(self, cmd, **args):
        #print "publish",cmd,args
        from networkServer import networkServer
        self.doPublish(cmd,args)
        networkServer.send("publish",(cmd,args))
        
    @classmethod
    def doPublish(self, cmd, args):
        if cmd in self._subscriptions:
            for f in self._subscriptions[cmd]:
                if callable(f):
                    f(args)
                else:
                    f.send((cmd,args))
        
    def stop(self):
        if not self.stopped:
            self._actionCount -= 1
            Service.stop(self)
            if self in self.object.actions:
                self.object.actions.remove(self)
                
    #wait for one var to change
    def waitForVar(self, name):
        self.wait(lambda: not name in self.object.dirtyVars)
        self.object.dirtyVars.remove(name)
        return self.object.getVar(name)
        
    #wait for a set of vars to change
    def waitForVars(self, names, comp = any):
        self.wait(lambda: not comp([(name in self.object.dirtyVars) for name in names]))
        map(self.object.dirtyVars.remove,filter(lambda name: name in self.object.dirtyVars, names))
        return map(self.object.getVar,names)
        
    #enumerate vars and return changed ones
    def enumerateChangedVars(self, names, comp = any, cond = None):
        while not cond or cond():
            self.wait(lambda: not comp([(name in self.object.dirtyVars) for name in names]))
            dirtyNames = filter(lambda name: name in self.object.dirtyVars, names)
            yield dict([(name, self.object.getVar(name)) for name in dirtyNames])
            map(self.object.dirtyVars.remove, dirtyNames)
                
    #enumerate vars and return all
    def enumerateVars(self, names, comp = any, cond = None):
        while not cond or cond():
            yield self.waitForVars(names, comp)
            
    def enumerateVarsAndTime(self, names, comp = any, cond = None):
        startTime = time.time()
        while not cond or cond():
            changedVars = self.waitForVars(names,comp)
            nextTime = time.time()
            yield nextTime - startTime, changedVars
            startTime = nextTime
            
    def log(self, *v):
        print "%s(%s): %s" % (self.object.name, self.name, ' '.join(map(str,v)))

